{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src=\"http://hilpisch.com/tpq_logo.png\" alt=\"The Python Quants\" width=\"35%\" align=\"right\" border=\"0\"><br>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Python for Finance"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Analyze Big Financial Data**\n",
    "\n",
    "O'Reilly (2014)\n",
    "\n",
    "Yves Hilpisch"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img style=\"border:0px solid grey;\" src=\"http://hilpisch.com/python_for_finance.png\" alt=\"Python for Finance\" width=\"30%\" align=\"left\" border=\"0\">"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Buy the book ** |\n",
    "<a href='http://shop.oreilly.com/product/0636920032441.do' target='_blank'>O'Reilly</a> |\n",
    "<a href='http://www.amazon.com/Yves-Hilpisch/e/B00JCYHHJM' target='_blank'>Amazon</a>\n",
    "\n",
    "**All book codes & IPYNBs** |\n",
    "<a href=\"http://oreilly.quant-platform.com\">http://oreilly.quant-platform.com</a>\n",
    "\n",
    "**The Python Quants GmbH** | <a href='http://pythonquants.com' target='_blank'>www.pythonquants.com</a>\n",
    "\n",
    "**Contact us** | <a href='mailto:analytics@pythonquants.com'>analytics@pythonquants.com</a>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "# Performance Python"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**REMARK**: This notebook has been changed to Python **3.4**; needs a cluster to be running under 'default' profile for the IPython.parallel example."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "collapsed": false,
    "uuid": "0e214dc5-2294-4526-847b-02d2b0b8ecf3"
   },
   "outputs": [],
   "source": [
    "def perf_comp_data(func_list, data_list, rep=3, number=1):\n",
    "    ''' Function to compare the performance of different functions.\n",
    "    \n",
    "    Parameters\n",
    "    ==========\n",
    "    func_list : list\n",
    "        list with function names as strings\n",
    "    data_list : list\n",
    "        list with data set names as strings\n",
    "    rep : int\n",
    "        number of repetitions of the whole comparison\n",
    "    number : int\n",
    "        number of executions for every function\n",
    "    '''\n",
    "    from timeit import repeat\n",
    "    res_list = {}\n",
    "    for name in enumerate(func_list):\n",
    "        stmt = name[1] + '(' + data_list[name[0]] + ')'\n",
    "        setup = \"from __main__ import \" + name[1] + ', ' \\\n",
    "                                    + data_list[name[0]]\n",
    "        results = repeat(stmt=stmt, setup=setup,\n",
    "                         repeat=rep, number=number)\n",
    "        res_list[name[1]] = sum(results) / rep\n",
    "    res_sort = sorted(res_list.items(),\n",
    "                      key=lambda x: (x[1], x[0]))\n",
    "    for item in res_sort:\n",
    "        rel = item[1] / res_sort[0][1]\n",
    "        print ('function: ' + item[0] +\n",
    "              ', av. time sec: %9.5f, ' % item[1]\n",
    "            + 'relative: %6.1f' % rel)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "## Python Paradigms and Performance"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "collapsed": false,
    "uuid": "b3334105-e18d-4510-a9c6-e879b8fb2484"
   },
   "outputs": [],
   "source": [
    "from math import *\n",
    "def f(x):\n",
    "    return abs(cos(x)) ** 0.5 + sin(2 + 3 * x)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "collapsed": false,
    "uuid": "06bd686b-de54-43e3-bd1f-4ef5fe006036"
   },
   "outputs": [],
   "source": [
    "I = 500000\n",
    "a_py = range(I)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "collapsed": false,
    "uuid": "887f94a7-954e-4a22-ad08-529686619217"
   },
   "outputs": [],
   "source": [
    "def f1(a):\n",
    "    res = []\n",
    "    for x in a:\n",
    "        res.append(f(x))\n",
    "    return res"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "collapsed": false,
    "uuid": "50d65fdf-ba74-4c09-910e-19560b3e2b78"
   },
   "outputs": [],
   "source": [
    "def f2(a):\n",
    "    return [f(x) for x in a]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "collapsed": false,
    "uuid": "c2dd4ea6-d303-4392-af37-a83411085463"
   },
   "outputs": [],
   "source": [
    "def f3(a):\n",
    "    ex = 'abs(cos(x)) ** 0.5 + sin(2 + 3 * x)'\n",
    "    return [eval(ex) for x in a]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "collapsed": false,
    "uuid": "f3bd9b18-bab2-433f-ab3c-4d7ffed24107"
   },
   "outputs": [],
   "source": [
    "import numpy as np"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "collapsed": false,
    "uuid": "e53390a4-d6ff-4083-bab3-c47955becc67"
   },
   "outputs": [],
   "source": [
    "a_np = np.arange(I)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "collapsed": false,
    "uuid": "701d5d58-e35c-4440-aa57-7bf96bcd546f"
   },
   "outputs": [],
   "source": [
    "def f4(a):\n",
    "    return (np.abs(np.cos(a)) ** 0.5 +\n",
    "            np.sin(2 + 3 * a))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "collapsed": false,
    "uuid": "0df68c60-ab06-4024-b7f8-cf3dacb67668"
   },
   "outputs": [],
   "source": [
    "import numexpr as ne"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "collapsed": false,
    "uuid": "ff4cbc78-b7c7-4a45-ab60-d143e59aa9f5"
   },
   "outputs": [],
   "source": [
    "def f5(a):\n",
    "    ex = 'abs(cos(a)) ** 0.5 + sin(2 + 3 * a)'\n",
    "    ne.set_num_threads(1)\n",
    "    return ne.evaluate(ex)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "collapsed": false,
    "uuid": "c4732030-2cf0-4fc2-923c-d341d7e70c09"
   },
   "outputs": [],
   "source": [
    "def f6(a):\n",
    "    ex = 'abs(cos(a)) ** 0.5 + sin(2 + 3 * a)'\n",
    "    ne.set_num_threads(16)\n",
    "    return ne.evaluate(ex)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "collapsed": false,
    "uuid": "cf287924-ddbc-4d2d-a1cc-5302da462211"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CPU times: user 7.76 s, sys: 43 ms, total: 7.8 s\n",
      "Wall time: 7.75 s\n"
     ]
    }
   ],
   "source": [
    "%%time\n",
    "r1 = f1(a_py)\n",
    "r2 = f2(a_py)\n",
    "r3 = f3(a_py)\n",
    "r4 = f4(a_np)\n",
    "r5 = f5(a_np)\n",
    "r6 = f6(a_np)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {
    "collapsed": false,
    "uuid": "77a761b5-8ba4-4377-8b97-36d4453cc3a5"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.allclose(r1, r2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {
    "collapsed": false,
    "uuid": "00b7a34b-224c-494b-8479-17e4f778e56b"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.allclose(r1, r3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {
    "collapsed": false,
    "uuid": "6b48a086-0d1c-46ca-9560-57c4f22373f2"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.allclose(r1, r4)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {
    "collapsed": false,
    "uuid": "3513b09a-7f41-413a-a3d2-5dc58487f5fa"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.allclose(r1, r5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {
    "collapsed": false,
    "uuid": "fe9798c0-3fc6-4a3e-94a9-65cbbe588709"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 18,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.allclose(r1, r6)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {
    "collapsed": false,
    "uuid": "2239301d-cf3c-4be3-b310-9001e5d346f2"
   },
   "outputs": [],
   "source": [
    "func_list = ['f1', 'f2', 'f3', 'f4', 'f5', 'f6']\n",
    "data_list = ['a_py', 'a_py', 'a_py', 'a_np', 'a_np', 'a_np']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {
    "collapsed": false,
    "uuid": "fd4bad14-de4c-4767-8f80-4ce8754a8531"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "function: f6, av. time sec:   0.00753, relative:    1.0\n",
      "function: f4, av. time sec:   0.03345, relative:    4.4\n",
      "function: f5, av. time sec:   0.03440, relative:    4.6\n",
      "function: f2, av. time sec:   0.27821, relative:   36.9\n",
      "function: f1, av. time sec:   0.29758, relative:   39.5\n",
      "function: f3, av. time sec:   7.21730, relative:  958.2\n"
     ]
    }
   ],
   "source": [
    "perf_comp_data(func_list, data_list)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "## Memory Layout and Performance"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {
    "collapsed": false,
    "uuid": "eae4309a-435c-4b76-a7cb-3d493ceb16a5"
   },
   "outputs": [],
   "source": [
    "import numpy as np"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {
    "collapsed": false,
    "uuid": "db307e1f-15f5-44ab-a35b-9f587e86f89f"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 0.,  0.,  0.],\n",
       "       [ 0.,  0.,  0.],\n",
       "       [ 0.,  0.,  0.]])"
      ]
     },
     "execution_count": 22,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.zeros((3, 3), dtype=np.float64, order='C')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {
    "collapsed": false,
    "slideshow": {
     "slide_type": "-"
    },
    "uuid": "ef951c18-ade7-4525-ae8f-6d1286aaa261"
   },
   "outputs": [],
   "source": [
    "c = np.array([[ 1.,  1.,  1.],\n",
    "              [ 2.,  2.,  2.],\n",
    "              [ 3.,  3.,  3.]], order='C')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {
    "collapsed": false,
    "uuid": "dd75848c-cbaf-4ff3-a023-b444fbaa2324"
   },
   "outputs": [],
   "source": [
    "f = np.array([[ 1.,  1.,  1.],\n",
    "              [ 2.,  2.,  2.],\n",
    "              [ 3.,  3.,  3.]], order='F')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {
    "collapsed": false,
    "uuid": "10424fde-31e8-4781-a7d8-eb8ec4469c10"
   },
   "outputs": [],
   "source": [
    "x = np.random.standard_normal((3, 150000))\n",
    "C = np.array(x, order='C')\n",
    "F = np.array(x, order='F')\n",
    "x = 0.0"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {
    "collapsed": false,
    "uuid": "82dda14d-3d8e-4953-a33b-ffece16a9de5"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1000 loops, best of 3: 255 µs per loop\n"
     ]
    }
   ],
   "source": [
    "%timeit C.sum(axis=0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {
    "collapsed": false,
    "uuid": "8e042104-1930-4a07-9467-6569f583414e"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "10000 loops, best of 3: 155 µs per loop\n"
     ]
    }
   ],
   "source": [
    "%timeit C.sum(axis=1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {
    "collapsed": false,
    "slideshow": {
     "slide_type": "-"
    },
    "uuid": "311be903-c939-4c1d-a8f7-22803299bd1d"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "100 loops, best of 3: 2.23 ms per loop\n"
     ]
    }
   ],
   "source": [
    "%timeit C.std(axis=0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {
    "collapsed": false,
    "uuid": "c032c8a5-c0af-4b75-a4a4-28d8b867738e"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1000 loops, best of 3: 782 µs per loop\n"
     ]
    }
   ],
   "source": [
    "%timeit C.std(axis=1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {
    "collapsed": false,
    "uuid": "65225c7d-e0ce-41f8-801d-16157e7725d4"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1000 loops, best of 3: 1.76 ms per loop\n"
     ]
    }
   ],
   "source": [
    "%timeit F.sum(axis=0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {
    "collapsed": false,
    "uuid": "11ce9f40-204a-4271-83b5-2f68c1a66415"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "100 loops, best of 3: 2.57 ms per loop\n"
     ]
    }
   ],
   "source": [
    "%timeit F.sum(axis=1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {
    "collapsed": false,
    "uuid": "9d9b7754-f8db-4477-a31b-38776c94ba03"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "100 loops, best of 3: 5.76 ms per loop\n"
     ]
    }
   ],
   "source": [
    "%timeit F.std(axis=0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {
    "collapsed": false,
    "uuid": "1ce86c04-2304-424d-b82c-409972b3f876"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "100 loops, best of 3: 6.3 ms per loop\n"
     ]
    }
   ],
   "source": [
    "%timeit F.std(axis=1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {
    "collapsed": false,
    "slideshow": {
     "slide_type": "skip"
    },
    "uuid": "5e25ec32-9655-4413-bc54-75adfa36daae"
   },
   "outputs": [],
   "source": [
    "C = 0.0; F = 0.0"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "## Parallel Computing"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### The Monte Carlo Algorithm"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {
    "collapsed": false,
    "uuid": "19106d2b-e618-45a1-85d3-978beb9f478e"
   },
   "outputs": [],
   "source": [
    "def bsm_mcs_valuation(strike):\n",
    "    ''' Dynamic Black-Scholes-Merton Monte Carlo estimator\n",
    "    for European calls.\n",
    "    \n",
    "    Parameters\n",
    "    ==========\n",
    "    strike : float\n",
    "        strike price of the option\n",
    "    \n",
    "    Results\n",
    "    =======\n",
    "    value : float\n",
    "        estimate for present value of call option\n",
    "    '''\n",
    "    import numpy as np\n",
    "    S0 = 100.; T = 1.0; r = 0.05; vola = 0.2\n",
    "    M = 50; I = 20000\n",
    "    dt = T / M\n",
    "    rand = np.random.standard_normal((M + 1, I))\n",
    "    S = np.zeros((M + 1, I)); S[0] = S0\n",
    "    for t in range(1, M + 1):\n",
    "        S[t] = S[t-1] * np.exp((r - 0.5 * vola ** 2) * dt\n",
    "                               + vola * np.sqrt(dt) * rand[t])\n",
    "    value = (np.exp(-r * T)\n",
    "                     * np.sum(np.maximum(S[-1] - strike, 0)) / I)\n",
    "    return value"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "### The Sequential Calculation"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {
    "collapsed": false,
    "uuid": "49a43195-200a-4cd4-a666-42fe73d00be6"
   },
   "outputs": [],
   "source": [
    "def seq_value(n):\n",
    "    ''' Sequential option valuation.\n",
    "    \n",
    "    Parameters\n",
    "    ==========\n",
    "    n : int\n",
    "        number of option valuations/strikes\n",
    "    '''\n",
    "    strikes = np.linspace(80, 120, n)\n",
    "    option_values = []\n",
    "    for strike in strikes:\n",
    "        option_values.append(bsm_mcs_valuation(strike))\n",
    "    return strikes, option_values"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {
    "collapsed": false,
    "uuid": "e15d7947-5a25-4a31-b0ff-83459837fe81"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CPU times: user 6.05 s, sys: 188 ms, total: 6.24 s\n",
      "Wall time: 6.24 s\n"
     ]
    }
   ],
   "source": [
    "n = 100  # number of options to be valued\n",
    "%time strikes, option_values_seq = seq_value(n)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {
    "collapsed": false,
    "uuid": "ce05a4cd-9d5c-4a18-82cc-8deab6418467"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<matplotlib.text.Text at 0x7fdf3b3d1b70>"
      ]
     },
     "execution_count": 38,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "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\nZh8iE3ahzwAAAABJRU5ErkJggg==\n",
      "text/plain": [
       "<matplotlib.figure.Figure at 0x7fdf7e75df28>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "plt.figure(figsize=(8, 4))\n",
    "plt.plot(strikes, option_values_seq, 'b')\n",
    "plt.plot(strikes, option_values_seq, 'r.')\n",
    "plt.grid(True)\n",
    "plt.xlabel('strikes')\n",
    "plt.ylabel('European call option values')\n",
    "# tag: option_values\n",
    "# title: European call option values by Monte Carlo simulation\n",
    "# size: 60"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "### The Parallel Calculation"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {
    "collapsed": false,
    "uuid": "624b0820-7879-45f3-9b7e-9df111ba2ab0"
   },
   "outputs": [],
   "source": [
    "from IPython.parallel import Client\n",
    "c = Client(profile=\"default\")\n",
    "view = c.load_balanced_view()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "metadata": {
    "collapsed": false,
    "uuid": "e6a46c36-aadb-4450-bb33-f046cac16b36"
   },
   "outputs": [],
   "source": [
    "def par_value(n):\n",
    "    ''' Sequential option valuation.\n",
    "    \n",
    "    Parameters\n",
    "    ==========\n",
    "    n : int\n",
    "        number of option valuations/strikes\n",
    "    '''\n",
    "    strikes = np.linspace(80, 120, n)\n",
    "    option_values = []\n",
    "    for strike in strikes:\n",
    "        value = view.apply_async(bsm_mcs_valuation, strike)\n",
    "        option_values.append(value)\n",
    "    c.wait(option_values)\n",
    "    return strikes, option_values"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "metadata": {
    "collapsed": false,
    "uuid": "9b34b0b0-5461-4f56-bff8-8dd61ffa4a16"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CPU times: user 687 ms, sys: 23 ms, total: 710 ms\n",
      "Wall time: 1.92 s\n"
     ]
    }
   ],
   "source": [
    "%time strikes, option_values_obj = par_value(n)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "metadata": {
    "collapsed": false,
    "uuid": "7482c419-1f0e-40c2-80fc-8e62ebe24421"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'after': [],\n",
       " 'completed': datetime.datetime(2015, 8, 8, 14, 24, 39, 288910),\n",
       " 'data': {},\n",
       " 'engine_id': 3,\n",
       " 'engine_uuid': '7aa98149-9b9c-4cd3-9dda-ca9e39e07fa6',\n",
       " 'error': None,\n",
       " 'execute_input': None,\n",
       " 'execute_result': None,\n",
       " 'follow': [],\n",
       " 'msg_id': 'a96e143c-27c7-405c-98e6-00f44cfb3cdb',\n",
       " 'outputs': [],\n",
       " 'outputs_ready': True,\n",
       " 'received': datetime.datetime(2015, 8, 8, 14, 24, 39, 291622),\n",
       " 'started': datetime.datetime(2015, 8, 8, 14, 24, 39, 193175),\n",
       " 'status': 'ok',\n",
       " 'stderr': '',\n",
       " 'stdout': '',\n",
       " 'submitted': datetime.datetime(2015, 8, 8, 14, 24, 39, 189905)}"
      ]
     },
     "execution_count": 42,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "option_values_obj[0].metadata"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "metadata": {
    "collapsed": false,
    "uuid": "9911cc43-347c-4d87-bd9a-31349116245f"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "24.638414768654542"
      ]
     },
     "execution_count": 43,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "option_values_obj[0].result"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "metadata": {
    "collapsed": false,
    "uuid": "de764249-8b40-4bf4-afc6-25442011a656"
   },
   "outputs": [],
   "source": [
    "option_values_par = []\n",
    "for res in option_values_obj:\n",
    "    option_values_par.append(res.result)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "metadata": {
    "collapsed": false,
    "uuid": "9bb28d37-52b5-4325-ab92-461a1952aebc"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<matplotlib.text.Text at 0x7fdf3b294940>"
      ]
     },
     "execution_count": 45,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAfMAAAEPCAYAAABWXy0pAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzt3Xl8VOX1+PHPCXvYhl2QJQiyWLFBxwU3Bq1UIypq41oV\ntY7WDWtb6lfboq11a7X+qK11XECrscRWxS3IaE3AFRdQFHQU2ZVFJEAAWc/vj3sThpjAkORm5t45\n79drXmTuLPc5vTYn9znPIqqKMcYYY/wrJ90NMMYYY0z9WDI3xhhjfM6SuTHGGONzlsyNMcYYn7Nk\nbowxxvicJXNjjDHG5zxL5iLSS0ReE5FPRORjEbnWPX6ziCwVkVnu40Sv2mCMMcZkA/FqnrmI7APs\no6qzRaQN8D4wGjgLWK+q93hyYmOMMSbLNPXqi1V1ObDc/blCROYB+7ovi1fnNcYYY7JNo9TMRSQP\nGAq87R66RkQ+FJGHRSTUGG0wxhhjgsrzZO52sf8HGKuqFcD9QF8gH/gauNvrNhhjjDFB5lnNHEBE\nmgEvACWqem8Nr+cBz6vqkGrHbcF4Y4wxWUdV61SG9nI0uwAPA3OTE7mIdE962+nAnJo+r6qBfYwf\nPz7tbbD4LL5sjC/IsVl8/n/Uh5fd7EcBPwVGJE1DOwm4U0Q+EpEPgeHAL2r8dEEBlJd72Lz0Wbhw\nYbqb4CmLz9+CHF+QYwOLL5t5OZr9dWr+Y6EkpS8oKWHV6VG6vFbcoO0yxhhjgsazZF5fq/LCjFwY\no91weKJ1lH03JJDWuVBUBCF/D4AfM2ZMupvgKYvP34IcX5BjA4svm3k6AK6uRER1zRq2tQkxeTLs\nf1mEwzaVOS8WFkKx3a0bY4wJFhFBM20AXL2FQjRtCuefD+FjcwGY3SzM6xfG0tyw+istLU13Ezxl\n8flbkOMLcmzgJAN7+OPR0DK2mz1Zzr+LIBpl9dkxfnpZiGgUfv97aNIk3S0zxpjMkom9rWZXXiTz\nzO1mr6VdX38NH4SdGvoPDs2l2VP+r6EbY0xDcLtp090Mswe1Xaf6dLP7LpkD6PAIMt2poW87o5Cm\n/7UaujHGWDL3By+SeebWzHdDWjs19Pkdw5xfEWPLljQ3aC8FvW5n8flbkOMLcmwmu/kymVNUBIWF\n9P40zqYWIS68EHb8LAqRSKAXmzHGGJNebdu2TWnxmoULF5KTk8OOHTu8bxQ+7WZP9t13Tv6+f16E\ngctt+poxJntlejf766+/zrhx45g7dy5NmjRh8ODB3HvvvYTD4XQ3rUaRSIQLLriASy+9dK8/u3Dh\nQvbbbz+2bdtGTs6u981edLP7YjT77rRsCVOmwJxeTtf79oPDNIn5f/qaMcYEybp16xg1ahQPPPAA\nZ511Fps3b2bGjBm0aNEi3U2rlRejzr3iz272atq2hSFzinindyFHVsSZsySzR7cHvW5n8flbkOML\ncmyZLpFIICKcffbZiAgtW7bkhBNOYMgQZ9PMRx55hAMOOICOHTty4oknsnjx4qrPxuNxBg0aRCgU\n4pprrmH48OE8/PDDANx8881ccMEFVe+t3r29du1aLr30Unr06EHPnj353e9+V/XapEmTOProo/n1\nr39Nx44d2W+//Zg6dSoAN910EzNmzODqq6+mbdu2XHvttQDk5OTw5ZdfAvDiiy8ydOhQ2rdvT+/e\nvbnllls8/l+xdoFI5gBte4U4fFExV90U4rjjYO7RUXR4xGroxhiTAQYOHEiTJk0YM2YMU6dOZc2a\nNVWvTZkyhdtvv51nnnmGb775hmOOOYZzzz0XgG+++YYzzzyT2267jdWrV9OvXz/efPPNqrvmPd09\njxkzhubNmzN//nxmzZrFtGnTeOihh6penzlzJoMGDWL16tWMGzeuqkv9T3/6E8cccwx///vfWb9+\nPRMmTPjed7dp04bHH3+ctWvX8uKLL3L//fczZcqUev9vVSfp3vKtlm3gtD4++0z13dbDVUEV9PUe\nhXr88arhsOqNN6pu316vrzfGmIyUyu9O99divR91MW/ePB0zZoz27NlTmzZtqqeeeqquWLFCTzzx\nRH344Yer3rd9+3bNzc3VRYsW6aOPPqrDhg3b5Xt69uxZ9f7x48frT3/606rXFixYoCKi27dv1+XL\nl2uLFi1006ZNVa8XFRXpiBEjVFV14sSJ2r9//6rXNmzYoCKiK1asUFXVSCSiDz300C7nFhGdP39+\njfGNHTtWf/GLX3yvHdXVdp3c43XKm4G5M082YAAMPcqpoa/KC7P69hg33ADPd49S+PcIs7oXsPEr\nu1s3xmSfhkrndTFo0CAmTpzIkiVL+Pjjj/nqq6+47rrrWLx4MWPHjqVDhw506NCBTp06AbBs2TK+\n/vprevbsucv39OrVK6XzLVq0iK1bt9K9e/eq777iiitYtWpV1Xv22Wefqp9zc528UVFRUXVsd3f+\n77zzDiNGjKBr166EQiEeeOABVq9enVLbGlogkzlAk8nO9LUus+KcemGIH/0I9lmXIH9tGYesLOGN\nA6MsX56etgW9bmfx+VuQ4wtybH4zcOBALrroIj7++GN69+5NLBZjzZo1VY8NGzYwbNgwunfvzpIl\nS6o+p6q7PG/Tpg0bN26ser486Rd7r169aNGiBatXr6763rVr1zJnzpyU2rinLvzzzjuP0aNHs3Tp\nUsrLy7niiisabSpadYFN5oRCzvS05KVe3b+6NBxm1hUxjjgCPv44Te0zxpgs8tlnn3HPPfewbNky\nAJYsWcKTTz7JsGHDuPzyy7ntttuYO3cu4Axae+qppwAoKCjgk08+4ZlnnmHbtm1MmDBhl4Sdn5/P\n9OnTWbJkCWvXruX222+veq179+6MHDmS66+/nvXr17Njxw7mz5/P9OnTU2pzt27dmD9/fq2vV1RU\n0KFDB5o3b87MmTMpKipK3wj4uvbPe/mgnjXzWq1Zo1pY6Pyrqk88odqxo+pVV6nOnevNKY0xprF4\n9ruzASxbtkzPOuss3XfffbV169a677776hVXXKHr169XVdV//etfOmTIEG3Xrp326tVLL7300qrP\nTp06VQcMGKDt27fXq6++WocPH75LLfuqq67SUCik+++/vz744IOak5NTVateu3at/vznP9eePXtq\n+/btdejQoTp58mRVVZ00aZIec8wxu7QzJyenqib+1ltv6YABA7RDhw46duxYVd21Zv6f//xH+/Tp\no23bttVRo0bpNddcoxdccIGqOjXz5HYkq+06UY+aue8XjamvivOjrHo9wZfLc7n3sCLGXBfitNOg\nqe9n4Btjsk2mLxrTUEaMGMEFF1zAJZdcku6m1Imtze6BNssS9F1cxvFbSrhva5Q774SkKYueCHrd\nzuLztyDHF+TYsk02/NGyN7I+mVfW0QmH6TM1RlkZzJ5tq8EaY0wm89PqbI0h67vZKS+HaBRiMWew\nXDTKuvcSvPtJLkPmFNF1QGavJmeMMZWypZvd72w/88YQiUCZs2HL6z0KOWppMfYHoDHGDyyZ+4PV\nzBuD2+2+45AwvwnFePxxnDv3BtxeNeh1O4vP34IcX5BjM9nNknl17l7pOa/Eue/xEL/8JWyek3Du\n1ktKnMRujDHGZBDrZt+DW26BEycUcPi3JWg4jMTjuy5EY4wxGcK62f3BauZpsHUr3HRVOSP+HeWy\n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\n",
      "text/plain": [
       "<matplotlib.figure.Figure at 0x7fdf3b301cf8>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "plt.figure(figsize=(8, 4))\n",
    "plt.plot(strikes, option_values_seq, 'b', label='Sequential')\n",
    "plt.plot(strikes, option_values_par, 'r.', label='Parallel')\n",
    "plt.grid(True); plt.legend(loc=0)\n",
    "plt.xlabel('strikes')\n",
    "plt.ylabel('European call option values')\n",
    "# tag: option_comp\n",
    "# title: Comparison of European call option values\n",
    "# size: 60"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Performance Comparison"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "metadata": {
    "collapsed": false,
    "uuid": "44fcd9c8-e4f1-4718-97dc-bdd3586f97d0"
   },
   "outputs": [],
   "source": [
    "n = 50  # number of option valuations\n",
    "func_list = ['seq_value', 'par_value']\n",
    "data_list = 2 * ['n']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "metadata": {
    "collapsed": false,
    "uuid": "72602c61-4194-443e-b8c2-0905f5de8081"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "function: par_value, av. time sec:   0.99212, relative:    1.0\n",
      "function: seq_value, av. time sec:   3.10869, relative:    3.1\n"
     ]
    }
   ],
   "source": [
    "perf_comp_data(func_list, data_list)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Multiprocessing"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "metadata": {
    "collapsed": false,
    "uuid": "d2fa8566-0084-408e-a2c8-dc8ec58ef460"
   },
   "outputs": [],
   "source": [
    "import multiprocessing as mp"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "metadata": {
    "collapsed": false,
    "uuid": "21c07619-d632-4208-9efd-a80ed68036a7"
   },
   "outputs": [],
   "source": [
    "import math\n",
    "def simulate_geometric_brownian_motion(p):\n",
    "    M, I = p\n",
    "      # time steps, paths\n",
    "    S0 = 100; r = 0.05; sigma = 0.2; T = 1.0\n",
    "      # model parameters\n",
    "    dt = T / M\n",
    "    paths = np.zeros((M + 1, I))\n",
    "    paths[0] = S0\n",
    "    for t in range(1, M + 1):\n",
    "        paths[t] = paths[t - 1] * np.exp((r - 0.5 * sigma ** 2) * dt +\n",
    "                    sigma * math.sqrt(dt) * np.random.standard_normal(I))\n",
    "    return paths"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 50,
   "metadata": {
    "collapsed": false,
    "uuid": "3b90f803-483d-4042-8365-831b14ae1284"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 100.        ,  100.        ],\n",
       "       [ 107.05898193,   95.40121142],\n",
       "       [ 105.12171976,   99.98421893],\n",
       "       [ 109.10202893,  116.52213994],\n",
       "       [ 101.98485643,  130.70542514],\n",
       "       [  90.82568096,  130.89253598]])"
      ]
     },
     "execution_count": 50,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "paths = simulate_geometric_brownian_motion((5, 2))\n",
    "paths"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "metadata": {
    "collapsed": false,
    "uuid": "0a4b66c3-3c89-49ea-871e-8c2856cc4d4e"
   },
   "outputs": [],
   "source": [
    "I = 10000  # number of paths\n",
    "M = 50  # number of time steps\n",
    "t = 20  # number of tasks/simulations"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "metadata": {
    "collapsed": false,
    "uuid": "428591ca-0dbd-4d12-bcfc-d611883df0cd"
   },
   "outputs": [],
   "source": [
    "# running on server with 4 cores\n",
    "from time import time\n",
    "times = []\n",
    "for w in range(1, 5):\n",
    "    t0 = time()\n",
    "    pool = mp.Pool(processes=w)\n",
    "      # the pool of workers\n",
    "    result = pool.map(simulate_geometric_brownian_motion, t * [(M, I), ])\n",
    "      # the mapping of the function to the list of parameter tuples\n",
    "    times.append(time() - t0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "metadata": {
    "collapsed": false,
    "uuid": "8ef9b38e-b367-447f-a5ac-cb05fb45dc67"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<matplotlib.text.Text at 0x7fdf3818ea90>"
      ]
     },
     "execution_count": 53,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<matplotlib.figure.Figure at 0x7fdf3b1ee470>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "plt.plot(range(1, 5), times)\n",
    "plt.plot(range(1, 5), times, 'ro')\n",
    "plt.grid(True)\n",
    "plt.xlabel('number of processes')\n",
    "plt.ylabel('time in seconds')\n",
    "plt.title('%d Monte Carlo simulations' % t)\n",
    "# tag: multi_proc\n",
    "# title: Comparison execution speed dependent on the number of threads used (4 core machine)\n",
    "# size: 60"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "## Dynamic Compiling"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "### Introductory Example"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 54,
   "metadata": {
    "collapsed": false,
    "uuid": "3f198c70-faa5-44a9-b886-227b97c2363d"
   },
   "outputs": [],
   "source": [
    "from math import cos, log\n",
    "def f_py(I, J):\n",
    "    res = 0\n",
    "    for i in range(I):\n",
    "        for j in range (J):\n",
    "            res += int(cos(log(1)))\n",
    "    return res"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 55,
   "metadata": {
    "collapsed": false,
    "uuid": "7ee49c0c-d153-445f-be3c-c1318e4766c8"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CPU times: user 1.87 s, sys: 3 ms, total: 1.87 s\n",
      "Wall time: 1.87 s\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "6250000"
      ]
     },
     "execution_count": 55,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "I, J = 2500, 2500\n",
    "%time f_py(I, J)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 56,
   "metadata": {
    "collapsed": false,
    "uuid": "1be826e5-f072-4476-b829-b12fb1b70086"
   },
   "outputs": [],
   "source": [
    "def f_np(I, J):\n",
    "    a = np.ones((I, J), dtype=np.float64)\n",
    "    return int(np.sum(np.cos(np.log(a)))), a"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 57,
   "metadata": {
    "collapsed": false,
    "uuid": "211eb198-f43d-4a89-b362-6e9507c099de"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CPU times: user 158 ms, sys: 8 ms, total: 166 ms\n",
      "Wall time: 165 ms\n"
     ]
    }
   ],
   "source": [
    "%time res, a = f_np(I, J)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 58,
   "metadata": {
    "collapsed": false,
    "uuid": "cfd19f8d-bf9a-442b-b3ef-2dfdce9ac3a9"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "50000000"
      ]
     },
     "execution_count": 58,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "a.nbytes"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 59,
   "metadata": {
    "collapsed": false,
    "uuid": "72e7a2be-97d0-4391-bc6e-3be881bf17a8"
   },
   "outputs": [],
   "source": [
    "import numba as nb"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 60,
   "metadata": {
    "collapsed": false,
    "uuid": "125becea-83ae-4718-9064-e1c3424888c3"
   },
   "outputs": [],
   "source": [
    "f_nb = nb.jit(f_py)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 61,
   "metadata": {
    "collapsed": false,
    "uuid": "aeb3d63e-7682-4d89-8c3a-0d5d8e649467"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CPU times: user 85 ms, sys: 2 ms, total: 87 ms\n",
      "Wall time: 86.5 ms\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "6250000"
      ]
     },
     "execution_count": 61,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "%time f_nb(I, J)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 62,
   "metadata": {
    "collapsed": false,
    "uuid": "25bc3375-a682-4f25-a303-b756848565d9"
   },
   "outputs": [],
   "source": [
    "func_list = ['f_py', 'f_np', 'f_nb']\n",
    "data_list = 3 * ['I, J']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 63,
   "metadata": {
    "collapsed": false,
    "uuid": "71e376e3-cd8c-47a8-93dc-5daeabd59e92"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "function: f_nb, av. time sec:   0.00000, relative:    1.0\n",
      "function: f_np, av. time sec:   0.16146, relative: 93409.9\n",
      "function: f_py, av. time sec:   1.86164, relative: 1077003.9\n"
     ]
    }
   ],
   "source": [
    "perf_comp_data(func_list, data_list)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Binomial Option Pricing"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 64,
   "metadata": {
    "collapsed": false,
    "uuid": "7340fbcb-1355-4862-ba6d-8bc04c04b014"
   },
   "outputs": [],
   "source": [
    "# model & option Parameters\n",
    "S0 = 100.  # initial index level\n",
    "T = 1.  # call option maturity\n",
    "r = 0.05  # constant short rate\n",
    "vola = 0.20  # constant volatility factor of diffusion\n",
    "\n",
    "# time parameters\n",
    "M = 1000  # time steps\n",
    "dt = T / M  # length of time interval\n",
    "df = exp(-r * dt)  # discount factor per time interval\n",
    "\n",
    "# binomial parameters\n",
    "u = exp(vola * sqrt(dt))  # up-movement\n",
    "d = 1 / u  # down-movement\n",
    "q = (exp(r * dt) - d) / (u - d)  # martingale probability"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 65,
   "metadata": {
    "collapsed": false,
    "uuid": "435a8933-8bbf-4e17-9f85-ff75f5997916"
   },
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "def binomial_py(strike):\n",
    "    ''' Binomial option pricing via looping.\n",
    "    \n",
    "    Parameters\n",
    "    ==========\n",
    "    strike : float\n",
    "        strike price of the European call option\n",
    "    '''\n",
    "    # LOOP 1 - Index Levels\n",
    "    S = np.zeros((M + 1, M + 1), dtype=np.float64)\n",
    "      # index level array\n",
    "    S[0, 0] = S0\n",
    "    z1 = 0\n",
    "    for j in range(1, M + 1, 1):\n",
    "        z1 = z1 + 1\n",
    "        for i in range(z1 + 1):\n",
    "            S[i, j] = S[0, 0] * (u ** j) * (d ** (i * 2))\n",
    "            \n",
    "    # LOOP 2 - Inner Values\n",
    "    iv = np.zeros((M + 1, M + 1), dtype=np.float64)\n",
    "      # inner value array\n",
    "    z2 = 0\n",
    "    for j in range(0, M + 1, 1):\n",
    "        for i in range(z2 + 1):\n",
    "            iv[i, j] = max(S[i, j] - strike, 0)\n",
    "        z2 = z2 + 1\n",
    "        \n",
    "    # LOOP 3 - Valuation\n",
    "    pv = np.zeros((M + 1, M + 1), dtype=np.float64)\n",
    "      # present value array\n",
    "    pv[:, M] = iv[:, M]  # initialize last time point\n",
    "    z3 = M + 1\n",
    "    for j in range(M - 1, -1, -1):\n",
    "        z3 = z3 - 1\n",
    "        for i in range(z3):\n",
    "            pv[i, j] = (q * pv[i, j + 1] +\n",
    "                        (1 - q) * pv[i + 1, j + 1]) * df\n",
    "    return pv[0, 0]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 66,
   "metadata": {
    "collapsed": false,
    "uuid": "9a193f03-655e-40ea-bac4-57869ddf4745"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CPU times: user 937 ms, sys: 2 ms, total: 939 ms\n",
      "Wall time: 938 ms\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "10.449"
      ]
     },
     "execution_count": 66,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "%time round(binomial_py(100), 3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 67,
   "metadata": {
    "collapsed": false,
    "uuid": "59c6c208-9d55-4822-a390-53a3d83d0ba0"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CPU times: user 61 ms, sys: 2 ms, total: 63 ms\n",
      "Wall time: 62.8 ms\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "10.452"
      ]
     },
     "execution_count": 67,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "%time round(bsm_mcs_valuation(100), 3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 68,
   "metadata": {
    "collapsed": false,
    "uuid": "8b7b292f-5d43-4462-9ac1-5ceb65ee078f"
   },
   "outputs": [],
   "source": [
    "def binomial_np(strike):\n",
    "    ''' Binomial option pricing with NumPy.\n",
    "    \n",
    "    Parameters\n",
    "    ==========\n",
    "    strike : float\n",
    "        strike price of the European call option\n",
    "    '''\n",
    "    # Index Levels with NumPy\n",
    "    mu = np.arange(M + 1)\n",
    "    mu = np.resize(mu, (M + 1, M + 1))\n",
    "    md = np.transpose(mu)\n",
    "    mu = u ** (mu - md)\n",
    "    md = d ** md\n",
    "    S = S0 * mu * md\n",
    "    \n",
    "    # Valuation Loop\n",
    "    pv = np.maximum(S - strike, 0)\n",
    "\n",
    "    z = 0\n",
    "    for t in range(M - 1, -1, -1):  # backwards iteration\n",
    "        pv[0:M - z, t] = (q * pv[0:M - z, t + 1]\n",
    "                        + (1 - q) * pv[1:M - z + 1, t + 1]) * df\n",
    "        z += 1\n",
    "    return pv[0, 0]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 69,
   "metadata": {
    "collapsed": false,
    "uuid": "61e7450e-58fb-4b5f-9f68-c95e82fe8070"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([0, 1, 2, 3, 4])"
      ]
     },
     "execution_count": 69,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "M = 4  # four time steps only\n",
    "mu = np.arange(M + 1)\n",
    "mu"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 70,
   "metadata": {
    "collapsed": false,
    "uuid": "0cad7263-b392-4b70-8e41-1b212c5bd076"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[0, 1, 2, 3, 4],\n",
       "       [0, 1, 2, 3, 4],\n",
       "       [0, 1, 2, 3, 4],\n",
       "       [0, 1, 2, 3, 4],\n",
       "       [0, 1, 2, 3, 4]])"
      ]
     },
     "execution_count": 70,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "mu = np.resize(mu, (M + 1, M + 1))\n",
    "mu"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 71,
   "metadata": {
    "collapsed": false,
    "uuid": "720863b6-f28c-46ad-8bf2-1743a0b09efc"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[0, 0, 0, 0, 0],\n",
       "       [1, 1, 1, 1, 1],\n",
       "       [2, 2, 2, 2, 2],\n",
       "       [3, 3, 3, 3, 3],\n",
       "       [4, 4, 4, 4, 4]])"
      ]
     },
     "execution_count": 71,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "md = np.transpose(mu)\n",
    "md"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 72,
   "metadata": {
    "collapsed": false,
    "uuid": "cad9aa29-b19d-4ab6-a26a-4664fa166a60"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 1.   ,  1.006,  1.013,  1.019,  1.026],\n",
       "       [ 0.994,  1.   ,  1.006,  1.013,  1.019],\n",
       "       [ 0.987,  0.994,  1.   ,  1.006,  1.013],\n",
       "       [ 0.981,  0.987,  0.994,  1.   ,  1.006],\n",
       "       [ 0.975,  0.981,  0.987,  0.994,  1.   ]])"
      ]
     },
     "execution_count": 72,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "mu = u ** (mu - md)\n",
    "mu.round(3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 73,
   "metadata": {
    "collapsed": false,
    "uuid": "fcf799c2-3311-4ca6-8293-1485caabe8a0"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 1.   ,  1.   ,  1.   ,  1.   ,  1.   ],\n",
       "       [ 0.994,  0.994,  0.994,  0.994,  0.994],\n",
       "       [ 0.987,  0.987,  0.987,  0.987,  0.987],\n",
       "       [ 0.981,  0.981,  0.981,  0.981,  0.981],\n",
       "       [ 0.975,  0.975,  0.975,  0.975,  0.975]])"
      ]
     },
     "execution_count": 73,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "md = d ** md\n",
    "md.round(3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 74,
   "metadata": {
    "collapsed": false,
    "uuid": "e6998500-9c10-4b10-85a2-1766153bd20a"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 100.   ,  100.634,  101.273,  101.915,  102.562],\n",
       "       [  98.743,   99.37 ,  100.   ,  100.634,  101.273],\n",
       "       [  97.502,   98.121,   98.743,   99.37 ,  100.   ],\n",
       "       [  96.276,   96.887,   97.502,   98.121,   98.743],\n",
       "       [  95.066,   95.669,   96.276,   96.887,   97.502]])"
      ]
     },
     "execution_count": 74,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "S = S0 * mu * md\n",
    "S.round(3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 75,
   "metadata": {
    "collapsed": false,
    "uuid": "29f7228e-7751-46dd-886e-e74c4c28d56b"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CPU times: user 144 ms, sys: 2 ms, total: 146 ms\n",
      "Wall time: 145 ms\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "10.449"
      ]
     },
     "execution_count": 75,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "M = 1000  # reset number of time steps\n",
    "%time round(binomial_np(100), 3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 76,
   "metadata": {
    "collapsed": false,
    "uuid": "0551702a-ecbb-46ec-8a65-eca2b9788adc"
   },
   "outputs": [],
   "source": [
    "binomial_nb = nb.jit(binomial_py)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 77,
   "metadata": {
    "collapsed": false,
    "uuid": "ea4be56f-18d2-46c0-8a56-23c24b7c1c9a"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CPU times: user 299 ms, sys: 2 ms, total: 301 ms\n",
      "Wall time: 300 ms\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "10.449"
      ]
     },
     "execution_count": 77,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "%time round(binomial_nb(100), 3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 78,
   "metadata": {
    "collapsed": false,
    "uuid": "6b0286e5-8f41-47e3-897a-0e9cf06ce35f"
   },
   "outputs": [],
   "source": [
    "func_list = ['binomial_py', 'binomial_np', 'binomial_nb']\n",
    "K = 100.\n",
    "data_list = 3 * ['K']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 79,
   "metadata": {
    "collapsed": false,
    "uuid": "bf398a3d-c2ce-4cdd-ae1e-0c7e49afcdf7"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "function: binomial_nb, av. time sec:   0.12441, relative:    1.0\n",
      "function: binomial_np, av. time sec:   0.14484, relative:    1.2\n",
      "function: binomial_py, av. time sec:   0.89842, relative:    7.2\n"
     ]
    }
   ],
   "source": [
    "perf_comp_data(func_list, data_list)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Static Compiling with Cython"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 80,
   "metadata": {
    "collapsed": false,
    "uuid": "42984b49-a970-4dd1-9ea0-cad5cae02323"
   },
   "outputs": [],
   "source": [
    "def f_py(I, J):\n",
    "    res = 0.  # we work on a float object\n",
    "    for i in range(I):\n",
    "        for j in range (J * I):\n",
    "            res += 1\n",
    "    return res"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 81,
   "metadata": {
    "collapsed": false,
    "uuid": "61275ff2-94d2-4e94-bdb2-caf356435074"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CPU times: user 8.53 s, sys: 9 ms, total: 8.54 s\n",
      "Wall time: 8.52 s\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "125000000.0"
      ]
     },
     "execution_count": 81,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "I, J = 500, 500\n",
    "%time f_py(I, J)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 82,
   "metadata": {
    "collapsed": false,
    "uuid": "215b732a-4fe7-4901-b054-e6c3b6ca2161"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(None, <pyximport.pyximport.PyxImporter at 0x7fdee67c15f8>)"
      ]
     },
     "execution_count": 82,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import pyximport\n",
    "pyximport.install()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 83,
   "metadata": {
    "collapsed": false,
    "uuid": "5adec88a-d48c-4972-abb7-0a6492384f13"
   },
   "outputs": [],
   "source": [
    "import sys\n",
    "sys.path.append('data/')\n",
    "  # path to the Cython script\n",
    "  # not needed if in same directory"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 84,
   "metadata": {
    "collapsed": false,
    "uuid": "a28ef97f-7867-4163-bba1-87706aa348ad"
   },
   "outputs": [],
   "source": [
    "from nested_loop import f_cy"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 85,
   "metadata": {
    "collapsed": false,
    "uuid": "cb96d228-6db0-4d9d-ac7e-852698da0098"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CPU times: user 104 ms, sys: 0 ns, total: 104 ms\n",
      "Wall time: 104 ms\n"
     ]
    }
   ],
   "source": [
    "%time res = f_cy(I, J)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 86,
   "metadata": {
    "collapsed": false,
    "uuid": "03e8d77b-b0d7-4dab-8b9d-f544dcd10d91"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "125000000.0"
      ]
     },
     "execution_count": 86,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "res"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 87,
   "metadata": {
    "collapsed": false,
    "uuid": "ce1d0d99-0fc1-4062-9f77-de253fa41ef7"
   },
   "outputs": [],
   "source": [
    "%load_ext Cython"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 88,
   "metadata": {
    "collapsed": false,
    "uuid": "2a20c9e7-69e5-47e5-a5f9-2271e42661e3"
   },
   "outputs": [],
   "source": [
    "%%cython\n",
    "#\n",
    "# Nested loop example with Cython\n",
    "#\n",
    "def f_cy(int I, int J):\n",
    "    cdef double res = 0\n",
    "    # double float much slower than int or long\n",
    "    for i in range(I):\n",
    "        for j in range (J * I):\n",
    "            res += 1\n",
    "    return res"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 89,
   "metadata": {
    "collapsed": false,
    "uuid": "44344a84-ccd1-47ce-a23a-69d70cdb6704"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CPU times: user 99 ms, sys: 1 ms, total: 100 ms\n",
      "Wall time: 99.8 ms\n"
     ]
    }
   ],
   "source": [
    "%time res = f_cy(I, J)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 90,
   "metadata": {
    "collapsed": false,
    "uuid": "a1b8b484-49cd-4872-ae0d-d547d6d965d1"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "125000000.0"
      ]
     },
     "execution_count": 90,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "res"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 91,
   "metadata": {
    "collapsed": false,
    "uuid": "f0984dae-8739-44db-bf67-dd9fc4ff058d"
   },
   "outputs": [],
   "source": [
    "import numba as nb"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 92,
   "metadata": {
    "collapsed": false,
    "uuid": "7f8097cb-dcca-4280-9371-a49a34c91f28"
   },
   "outputs": [],
   "source": [
    "f_nb = nb.jit(f_py)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 93,
   "metadata": {
    "collapsed": false,
    "uuid": "ab30b87b-1ef5-41b7-918d-95e096cbafaf"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CPU times: user 135 ms, sys: 0 ns, total: 135 ms\n",
      "Wall time: 134 ms\n"
     ]
    }
   ],
   "source": [
    "%time res = f_nb(I, J)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 94,
   "metadata": {
    "collapsed": false,
    "uuid": "d8c59388-1209-4d26-af73-13ee504a89f2"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "125000000.0"
      ]
     },
     "execution_count": 94,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "res"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 95,
   "metadata": {
    "collapsed": false,
    "uuid": "86494215-2f3c-4299-83dd-a74b58847658"
   },
   "outputs": [],
   "source": [
    "func_list = ['f_py', 'f_cy', 'f_nb']\n",
    "I, J = 500, 500\n",
    "data_list = 3 * ['I, J']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 96,
   "metadata": {
    "collapsed": false,
    "uuid": "7fdcda66-f27e-4f77-a103-b9c862ed1b33"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "function: f_nb, av. time sec:   0.09966, relative:    1.0\n",
      "function: f_cy, av. time sec:   0.10063, relative:    1.0\n",
      "function: f_py, av. time sec:   8.58588, relative:   86.1\n"
     ]
    }
   ],
   "source": [
    "perf_comp_data(func_list, data_list)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "## Generation of Random Numbers on GPUs"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "## Conclusions"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Further Reading"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src=\"http://hilpisch.com/tpq_logo.png\" alt=\"The Python Quants\" width=\"35%\" align=\"right\" border=\"0\"><br>\n",
    "\n",
    "<a href=\"http://www.pythonquants.com\" target=\"_blank\">www.pythonquants.com</a> | <a href=\"http://twitter.com/dyjh\" target=\"_blank\">@dyjh</a>\n",
    "\n",
    "<a href=\"mailto:analytics@pythonquants.com\">analytics@pythonquants.com</a>\n",
    "\n",
    "**Python Quant Platform** |\n",
    "<a href=\"http://oreilly.quant-platform.com\">http://oreilly.quant-platform.com</a>\n",
    "\n",
    "**Derivatives Analytics with Python** |\n",
    "<a href=\"http://www.derivatives-analytics-with-python.com\" target=\"_blank\">Derivatives Analytics @ Wiley Finance</a>\n",
    "\n",
    "**Python for Finance** |\n",
    "<a href=\"http://shop.oreilly.com/product/0636920032441.do\" target=\"_blank\">Python for Finance @ O'Reilly</a>"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.4.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}
